﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

using NPipeline.Event;


 
namespace NPipeline
{
    /// <summary>
    /// 
    /// 
    /// </summary>
    public class Pipeline : IStageContext
    {
        public static string MAIN_BRANCH { get { return "main"; } }

        // add a logger instance -- good would be to use the commons logging ??

        private LinkedList<IStageDriver> drivers;
        private Dictionary<IStage, IStageDriver> driverMap;

        private LinkedList<IStage> stages;

        private Dictionary<String, Pipeline> branches;

        private Pipeline parent;

        // define the start and stop events
        public event PipelineLifeCycleEventHandler PipelineStart;
        public event PipelineLifeCycleEventHandler PipelineFinish;

        public event StageEventHandler StageEvent;
        public event StageErrorEventHandler StageErrorEvent;

        // should events be raised syncronous or asyncrounous
        private bool asyncEvents = false;

        // stage event listeners -- shouldn't that be a event ??
        //private List<StageEventListeners> listeners;

        // need to check the concept of a validator first to see what it does
        // private PipelineValidator validator;

        private IFeeder terminalFeeder = new TerminalFeeder();
        private Dictionary<String, object> env = new Dictionary<string, object>();

        // List of jobs to be run at defined points in the pipeline lifecycle
        // not done yet
        // private List<PipelineLifeCycleJob> lifeCycleJobs = new List<PipelineLifeCycleJob>();

        /// <summary>
        /// Runs the pipeline 
        /// 
        /// <code>
        ///   var p = new Pipeline();
        ///   p.AddStage(new ConsoleOutputStage(), new GenericStageDriverFactory<SynchronousStageDriver>());
        ///   p.getSourceFeeder().Feed("Hello World");
        ///   p.Run()
        /// </code>
        /// </summary>
        public Pipeline()
        {
            drivers = new LinkedList<IStageDriver>();
            driverMap = new Dictionary<IStage, IStageDriver>();
            stages = new LinkedList<IStage>();
            branches = new Dictionary<string, Pipeline>();
            // initialiaze also the listeners ... do we need to if we use
            // events ??


        }

        /// <summary>
        /// returns the first feeder of the pipeline or the terminal one
        /// if not configured
        /// </summary>
        /// <returns></returns>
        public IFeeder getSourceFeeder()
        {
            if (drivers.Count == 0) return this.terminalFeeder;
            return drivers.First.Value.Feeder;
        }


        #region IStageContext Members

        
        // we really want to asyncrounosly notify all all registers listeners
        // if we use events -- wouldn't that be simply BeginInvoke on the event ?
        // or call Raise on the event itself ?
        public void RaiseStageEvent(StageEventArgs ev)
        {
            // this would raise an event this actually should go back to the main pipeline and
            // raise the event
            Pipeline root = this;
            while (root.parent != null) { root = root.parent; }
            // look at the eventmodel if this is really required
            root.notifyStageListeners(ev);

        }
        
        
        // following is questionable and not part of the Interface
        private void notifyStageListeners(StageEventArgs ev) 
        {
            // Really need to look at the event model and what it is used for.
            // foreach (StageEventListener listener in listeners) { listener.notify(ev) };
            // since we have events - just go ahead and call the event
            if (StageEvent != null)
                // check if we should fire events asyncrounously
                // just NULL for the ASyncResult ??
                // TODO -- is this really smart ?? -- seems to work ...
                if (asyncEvents)
                    StageEvent.BeginInvoke(this, ev, null, null);
                else
                    StageEvent(this, ev);

            // now raise the same event on all branches
            foreach (Pipeline branch in branches.Values) { branch.notifyStageListeners(ev); };
        }


        // we really want to asyncrounosly notify all all registers listeners
        // if we use events -- wouldn't that be simply BeginInvoke on the event ?
        // or call Raise on the event itself ?
        public void RaiseStageErrorEvent(StageErrorEventArgs ev)
        {
            // this would raise an event this actually should go back to the main pipeline and
            // raise the event
            Pipeline root = this;
            while (root.parent != null) { root = root.parent; }
            // look at the eventmodel if this is really required
            root.notifyStageErrorListeners(ev);

        }


        // following is questionable and not part of the Interface
        private void notifyStageErrorListeners(StageErrorEventArgs ev)
        {
            // Really need to look at the event model and what it is used for.
            // foreach (StageEventListener listener in listeners) { listener.notify(ev) };
            // since we have events - just go ahead and call the event
            if (StageErrorEvent != null)
                if (asyncEvents)
                    StageErrorEvent.BeginInvoke(this, ev, null, null);
                else
                    StageErrorEvent(this, ev);
            // now raise the same event on all branches
            foreach (Pipeline branch in branches.Values) { branch.notifyStageErrorListeners(ev); };
        }


        
        
        public IFeeder getBranchFeeder(string branch)
        {
            if (!branches.ContainsKey(branch))
                throw new ArgumentException(
                    String.Format("Unable to find branch in pipeline [%0]", branch), "branch");

            return branches[branch].getSourceFeeder();

        }

        public IFeeder getDownstreamFeeder(IStage stage)
        {
            if (stage == null) throw new ArgumentNullException("stage");
            if (stage == drivers.Last.Value.Stage)
            {
                return this.terminalFeeder;
            }
            else
            {
                // iterate backwards over the list until the stage is found,
                // then return the feeder for the subsequent stage. Comparison is
                // done using reference equality

                // look at that logic ... doesn't look nice - is esentially a 
                // reverse iterate by going to previous
                // we are going through the linked list in reverse as we are
                // looking for a IStageDriver that has the Stage, then we are going
                // to the next one and return the Feeder
                
                // this link is usually cached in the Feeder, so the lookup is just
                // done once for the first object. This has been disabled as it makes
                // the pipeline imutable after you run object through.
                // has to be revistiged

                LinkedListNode<IStageDriver> current = drivers.Last;
                current = current.Previous;

                while (current != null)
                {
                    if (stage == current.Value.Stage)
                        return current.Next.Value.Feeder;
                    current = current.Previous;
                }
                // no stagedriver and Stage found
                throw new ArgumentException("Unable to find stage in pipeline", "stage");
            }
        }

        public IStageDriver getStageDriver(IStage stage)
        {
            // returns null if Stage is not hooked up to a StageDriver
            try
            {
                return drivers.First((check) => { return (check.Stage == stage); });
            }
            catch (InvalidOperationException)
            {
                // ignore not found and return null
                return null;
            }
        }

        // that doesn't work ... should we expose a specific method or the whole
        // collection ... the access needs to be thread safe how can we do that ?
        // lock the access ?
        public object Env
        {
            get { return env; }
        }

        #endregion

        // we are missing the lifecycle jobs here -- again - is simply a list of 
        // objects we should call in the event that we want to do something. gives nice
        // extensiblity - but not required for the basic functionality

        public void Start()
        {
            // here we would call the LifeCycleJobs  OnStart(this) method
            OnStart();

            // start all the drivers
            foreach (IStageDriver driver in drivers) driver.Start();
            // start all the branch pipelines
            foreach (Pipeline branch in branches.Values) branch.Start();

        }

        /// <summary>
        /// This method iterates over the stages in the pipeline, looking up a {@link StageDriver}
        /// * for each stage and using that driver to request that the stage finish
        /// * execution. The {@link StageDriver#finish(Stage)}
        /// * method will block until the stage's queue is exhausted, so this method
        /// * may be used to safely finalize all stages without the risk of
        /// * losing data in the queues.
        /// </summary>
        public void Finish()
        {
            foreach (IStageDriver driver in drivers) driver.Finish();
            foreach (Pipeline branch in branches.Values) branch.Finish();
            // here we would call the lifecycle jobs and notify them what is happening.
            OnFinish();

        }

        /// <summary>
        /// Runs the pipeline from start to finish
        /// </summary>
        public void Run()
        {
            try
            {
                Start();
                Finish();
            }
            catch (StageException e) {
                // this should be a different exception ??
                throw e;
            }
        }


        // do we need a validator ??

        /// <summary>
        /// Returns the parent of this pipeline, if this is a branch
        /// otherwise it will be null
        /// </summary>
        public Pipeline Parent 
        { 
            get { return this.parent; }
            set { this.parent = value; }
        }


        // event handler functions
        protected void OnStart()
        {
            Pipeline root = this;
            while (root.Parent != null) root = root.Parent;

            if (root.PipelineStart != null)
            {
                // create the EventArgs - so you know from WHICH pipeline the event comes ...
                Event.PipelineLifeCycleEventArgs e = new Event.PipelineLifeCycleEventArgs(this);
                root.PipelineStart(root, e);
            }
        }

        protected void OnFinish()
        {
            // bubble event up to Main branch -- DON'T forget to handle the Add/Remove on 
            // the event properties so all subscriptions end up on the MAIN pipeline ??
            // refactor to method that finds the MAIN pipeline
            Pipeline root = this;
            while (root.Parent != null) root = root.Parent;

            if (root.PipelineFinish != null)
            {
                Event.PipelineLifeCycleEventArgs e = new Event.PipelineLifeCycleEventArgs(this);
                root.PipelineFinish(root, e);
            }
        }


        // we really need a factory here to create the StageDriver
        public void AddStage(IStage stage, IStageDriverFactory driverFactory)
        {
            // check if the stage is provide
            if (stage == null) throw new ArgumentNullException("stage");

            // we should do some validation here -- missing


            // add the stage -- there should be really a driver factory
            // init the stage with the pipelines context
            stage.Init(this);
            this.stages.AddLast(stage);

            // what about exception handling ??

            // create the driver via the Provided Factory
            IStageDriver driver = driverFactory.CreateDriver(stage, this);
            this.driverMap.Add(stage, driver);
            this.drivers.AddLast(driver);

        }


        // information functions -- not very effective as we copy the list ...
        public ReadOnlyCollection<IStage> Stages { get { return stages.ToList<IStage>().AsReadOnly(); } }
        public ReadOnlyCollection<IStageDriver> Drivers { get { return drivers.ToList<IStageDriver>().AsReadOnly(); } }


        // Branch handling

        public void AddBranch(String key, Pipeline branch)
        {

            if (key == null) throw new ArgumentNullException("key");
            if (MAIN_BRANCH == key)
                throw new ArgumentException(String.Format("Branch key name {0} is reserved", MAIN_BRANCH));
            if (branch == null)
                throw new ArgumentNullException("branch");

            if (branch == this || branch.hasBranch(this))
            {
                throw new ArgumentException("Illegal attempt to set reference to self as branch (infinite recursion)");
            }

            // hook up the branch
            branch.Parent = this;
            branches.Add(key, branch);

        }

        public bool hasBranch(Pipeline pipeline)
        {
            if (branches.ContainsValue(pipeline)) return true;
            // iterate over children
            foreach (Pipeline branch in branches.Values)
            {
                if (branch.hasBranch(pipeline)) return true;
            }
            return false;
        }

        // automatic property ???
        public bool AsyncEvents
        {
            get { return asyncEvents; }
            set { asyncEvents = value; }
        }



    }
}
